Skip to content
26 changes: 17 additions & 9 deletions contracts/src/WLSETH.1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ contract WLSETHV1 is IWLSETHV1, Initializable, ReentrancyGuardUpgradeable {
if (_to == address(0)) {
revert UnauthorizedTransfer(msg.sender, address(0));
}
return _transfer(msg.sender, _to, _value);
_transfer(msg.sender, _to, _value);
return true;
}

/// @inheritdoc IWLSETHV1
Expand All @@ -101,8 +102,11 @@ contract WLSETHV1 is IWLSETHV1, Initializable, ReentrancyGuardUpgradeable {
if (_to == address(0)) {
revert UnauthorizedTransfer(_from, address(0));
}
_spendAllowance(_from, _value);
return _transfer(_from, _to, _value);
bool movedShares = _transfer(_from, _to, _value);
if (movedShares) {
_spendAllowance(_from, _value);
Comment thread
iamsahu marked this conversation as resolved.
Outdated
}
Comment thread
iamsahu marked this conversation as resolved.
return true;
Comment thread
iamsahu marked this conversation as resolved.
}

/// @inheritdoc IWLSETHV1
Expand Down Expand Up @@ -211,11 +215,15 @@ contract WLSETHV1 is IWLSETHV1, Initializable, ReentrancyGuardUpgradeable {
revert Denied(_to);
}
uint256 valueToShares = river.sharesFromUnderlyingBalance(_value);
BalanceOf.set(_from, BalanceOf.get(_from) - valueToShares);
BalanceOf.set(_to, BalanceOf.get(_to) + valueToShares);

emit Transfer(_from, _to, _value);

return true;
if (valueToShares > 0) {
BalanceOf.set(_from, BalanceOf.get(_from) - valueToShares);
BalanceOf.set(_to, BalanceOf.get(_to) + valueToShares);

emit Transfer(_from, _to, _value);
Comment thread
iamsahu marked this conversation as resolved.
return true;
} else {
emit Transfer(_from, _to, 0);
Comment thread
iamsahu marked this conversation as resolved.
return false;
Comment thread
iamsahu marked this conversation as resolved.
}
Comment thread
iamsahu marked this conversation as resolved.
Comment thread
iamsahu marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
167 changes: 167 additions & 0 deletions contracts/test/WLSETH.1.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,173 @@ contract WLSETHV1Tests is WLSETHV1TestBase {
assert(balance == 0 ether);
}
}

function testMintTransferEventEmitsUnderlyingAfterRebase(uint256 _guySalt) external {
address _guy = uf._new(_guySalt);
// sudoSetBalance sets _guy's balance AND updates totalSupply
// So if we set balance to 50 shares, totalSupply becomes 50
// With underlyingTotal = 100 ether, ratio is 2:1 (underlying:shares)
// Thus 50 shares = 100 ether underlying
uint256 shares = 50 ether;
RiverTokenMock(address(river)).sudoSetBalance(_guy, shares);
Comment thread
iamsahu marked this conversation as resolved.

vm.startPrank(_guy);
RiverTokenMock(address(river)).approve(address(wlseth), shares);

// Transfer event should emit underlying value (100 ether), not shares (50 ether)
vm.expectEmit(true, true, true, true);
emit Transfer(address(0), _guy, 100 ether);
wlseth.mint(_guy, shares);
vm.stopPrank();

Comment thread
iamsahu marked this conversation as resolved.
Outdated
// Verify the Mint event still emits shares
Comment thread
iamsahu marked this conversation as resolved.
Outdated
Comment thread
iamsahu marked this conversation as resolved.
Outdated
assert(wlseth.sharesOf(_guy) == shares);
assert(wlseth.balanceOf(_guy) == 100 ether);
}

function testBurnTransferEventEmitsUnderlyingAfterRebase(uint256 _guySalt) external {
address _guy = uf._new(_guySalt);
uint256 shares = 100 ether;
RiverTokenMock(address(river)).sudoSetBalance(_guy, shares);

vm.startPrank(_guy);
RiverTokenMock(address(river)).approve(address(wlseth), shares);
wlseth.mint(_guy, shares);
vm.stopPrank();

// Simulate rebase: 2:1 underlying:shares ratio
RiverTokenMock(address(river)).sudoSetUnderlyingTotal(200 ether);

uint256 sharesToBurn = 50 ether;
// Transfer event should emit underlying value (100 ether), not shares (50 ether)
vm.startPrank(_guy);
vm.expectEmit(true, true, true, true);
emit Transfer(_guy, address(0), 100 ether);
wlseth.burn(_guy, sharesToBurn);
vm.stopPrank();
}

function testTransferEventEmitsUnderlyingValue(uint256 _guySalt, uint256 _recipientSalt) external {
address _guy = uf._new(_guySalt);
address _recipient = uf._new(_recipientSalt);
uint256 shares = 100 ether;
RiverTokenMock(address(river)).sudoSetBalance(_guy, shares);

vm.startPrank(_guy);
RiverTokenMock(address(river)).approve(address(wlseth), shares);
wlseth.mint(_guy, shares);
vm.stopPrank();

// Simulate rebase: 2:1 underlying:shares ratio
RiverTokenMock(address(river)).sudoSetUnderlyingTotal(200 ether);
uint256 guyBalance = wlseth.balanceOf(_guy);
assert(guyBalance == 200 ether);

// Transfer 100 ether (underlying), which is 50 shares at 2:1 ratio
vm.startPrank(_guy);
vm.expectEmit(true, true, true, true);
emit Transfer(_guy, _recipient, 100 ether);
wlseth.transfer(_recipient, 100 ether);
vm.stopPrank();

assert(wlseth.balanceOf(_guy) == 100 ether);
assert(wlseth.balanceOf(_recipient) == 100 ether);
}

function testTransferFromEventEmitsUnderlyingValue(uint256 _fromSalt, uint256 _approvedSalt, uint256 _recipientSalt)
external
{
address _from = uf._new(_fromSalt);
address _approved = uf._new(_approvedSalt);
address _recipient = uf._new(_recipientSalt);
uint256 shares = 100 ether;
RiverTokenMock(address(river)).sudoSetBalance(_from, shares);

vm.startPrank(_from);
RiverTokenMock(address(river)).approve(address(wlseth), shares);
wlseth.mint(_from, shares);
vm.stopPrank();

// Simulate rebase: 2:1 underlying:shares ratio
RiverTokenMock(address(river)).sudoSetUnderlyingTotal(200 ether);

vm.prank(_from);
wlseth.approve(_approved, 100 ether);

// Transfer 100 ether (underlying) via transferFrom
vm.startPrank(_approved);
vm.expectEmit(true, true, true, true);
emit Transfer(_from, _recipient, 100 ether);
wlseth.transferFrom(_from, _recipient, 100 ether);
vm.stopPrank();

assert(wlseth.balanceOf(_from) == 100 ether);
assert(wlseth.balanceOf(_recipient) == 100 ether);
}

function testTransferEmitsZeroWhenValueTooSmallForShares(uint256 _guySalt, uint256 _recipientSalt) external {
address _guy = uf._new(_guySalt);
address _recipient = uf._new(_recipientSalt);
uint256 shares = 100 ether;
RiverTokenMock(address(river)).sudoSetBalance(_guy, shares);

vm.startPrank(_guy);
RiverTokenMock(address(river)).approve(address(wlseth), shares);
wlseth.mint(_guy, shares);
vm.stopPrank();

// Set up ratio where small underlying amounts don't convert to shares
// underlyingTotal = 1000 ether, totalSupply = 100 ether
// sharesFromUnderlyingBalance(1 wei) = (1 * 100 ether) / 1000 ether = 0
RiverTokenMock(address(river)).sudoSetUnderlyingTotal(1000 ether);

// User has 1000 ether balance now, try to transfer 1 wei
// This converts to 0 shares, so Transfer event emits 0
vm.startPrank(_guy);
vm.expectEmit(true, true, true, true);
emit Transfer(_guy, _recipient, 0);
wlseth.transfer(_recipient, 1);
vm.stopPrank();

// Balances unchanged since 0 shares transferred
assert(wlseth.balanceOf(_recipient) == 0);
Comment thread
iamsahu marked this conversation as resolved.
}
Comment thread
iamsahu marked this conversation as resolved.

function testTransferFromEmitsZeroWhenValueTooSmallForShares(
uint256 _fromSalt,
uint256 _approvedSalt,
uint256 _recipientSalt
) external {
address _from = uf._new(_fromSalt);
address _approved = uf._new(_approvedSalt);
address _recipient = uf._new(_recipientSalt);
uint256 shares = 100 ether;
RiverTokenMock(address(river)).sudoSetBalance(_from, shares);

vm.startPrank(_from);
RiverTokenMock(address(river)).approve(address(wlseth), shares);
wlseth.mint(_from, shares);
vm.stopPrank();

// Set up ratio where small underlying amounts don't convert to shares
RiverTokenMock(address(river)).sudoSetUnderlyingTotal(1000 ether);

vm.prank(_from);
wlseth.approve(_approved, 1);

// Transfer 1 wei via transferFrom, converts to 0 shares
vm.startPrank(_approved);
vm.expectEmit(true, true, true, true);
emit Transfer(_from, _recipient, 0);
wlseth.transferFrom(_from, _recipient, 1);
vm.stopPrank();
Comment thread
iamsahu marked this conversation as resolved.

// Balances unchanged since 0 shares transferred
assert(wlseth.balanceOf(_recipient) == 0);
assert(wlseth.sharesOf(_recipient) == 0);
assert(wlseth.sharesOf(_from) == 100 ether);
assert(wlseth.balanceOf(_from) == 1000 ether);
Comment thread
iamsahu marked this conversation as resolved.
}
Comment thread
iamsahu marked this conversation as resolved.
Comment thread
iamsahu marked this conversation as resolved.
}

contract WLSETHV1DenyTests is WLSETHV1TestBase {
Expand Down
Loading